/**********************************************************************
 *
 * This code is developed for the course Informatica Industrial II
 * of Universidad Carlos III de Madrid  - 2012
 * Authors: Victor González-Pacheco, Alberto Valero-Gomez
 *
 * The code is licenced under the Common Creative License,
 * Attribution-ShareAlike 3.0
 *
 * You are free:
 *   - to Share - to copy, distribute and transmit the work
 *   - to Remix - to adapt the work
 *
 * Under the following conditions:
 *   - Attribution. You must attribute the work in the manner specified
 *     by the author or licensor (but not in any way that suggests that
 *     they endorse you or your use of the work).
 *   - Share Alike. If you alter, transform, or build upon this work,
 *     you may distribute the resulting work only under the same or
 *     similar license to this one.
 *
 * Any of the above conditions can be waived if you get permission
 * from the copyright holder.  Nothing in this license impairs or
 * restricts the author's moral rights.
 *
 * It is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied
 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
 * PURPOSE.
 **********************************************************************/


#include <string.h>
#include <sys/socket.h>
#include <sstream>
#include <netdb.h>
#include <arpa/inet.h>
#include <cerrno>
#include <cstring>


#include "TCPSocketServer.h"



TCPSocketServer::TCPSocketServer(int port, int max_connections, string ip) throw (char const*):
    _max_connections(max_connections),
    _binded(false),
    _listening(false)
{
    try{
        _incoming_socket = new TCPSocket("",0,ip,port);
        bind();
        listen();
    }catch(char const* e){
        throw(e);
    }
}

bool TCPSocketServer::bind() throw(char const *){

    if(_binded) return _binded; //already connected

    int inc_socket_fd = _incoming_socket->getFileDescriptor();
    int local_port = _incoming_socket->getLocalPort();
    string local_ip = _incoming_socket->getLocalIP();

    socklen_t clilen;
    char buffer[256];
    struct sockaddr_in serv_addr, cli_addr;

    bzero((char *) &serv_addr, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = INADDR_ANY;
    serv_addr.sin_port = htons(local_port);
    if (::bind(inc_socket_fd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) {
        _binded=false;

        stringstream str;
        str << "Error binding "
               << inet_ntoa(serv_addr.sin_addr)
               << ":"<<ntohs(serv_addr.sin_port)
               << "\n"
               << "socket file descriptor: "
               << _incoming_socket->getFileDescriptor()
               << "\n"
               << "Error number: "
               << strerror(errno)
               << " "
               << errno
                  ;
        throw(str.str().c_str());
    }
    _binded = true;
    cout << "server: " << local_ip << ":" << local_port <<" binded." << endl;
    return _binded;
}

int TCPSocketServer::accept() throw (char const *){

    int sockfd =_incoming_socket->getFileDescriptor();
    int portno = _incoming_socket->getLocalPort();

    int newsockfd; //new socket file descriptor for the connection we want to accept


    socklen_t clilen; //size of the address of the client
    char buffer[256];

    struct sockaddr_in cli_addr; //client address

    clilen = sizeof(cli_addr);

    newsockfd = ::accept(sockfd,(struct sockaddr *) &cli_addr ,&clilen);
    if (newsockfd < 0) {
        stringstream str;
        str << "Error accepting connections \n"
               << "Error number: "
               << strerror(errno)
               << " "
               << errno
                  ;
        throw(str.str().c_str());
    }



    //get IP and Port of client

    char client_ip[INET6_ADDRSTRLEN]; //client ip address
    int client_port; //client port

    // deal with both IPv4 and IPv6:

    //string client_ip = inet_ntoa(cli_addr.sin_addr.s_addr);


    if (cli_addr.sin_family == AF_INET) {
        struct sockaddr_in *s = (struct sockaddr_in *)&cli_addr;
        client_port = ntohs(s->sin_port);
        inet_ntop(AF_INET, &s->sin_addr, client_ip, sizeof client_ip);
    } else { // AF_INET6
        struct sockaddr_in6 *s = (struct sockaddr_in6 *)&cli_addr;
        client_port = ntohs(s->sin6_port);
        inet_ntop(AF_INET6, &s->sin6_addr, client_ip, sizeof client_ip);
    }

    string server_ip = _incoming_socket->getLocalIP();
    int server_port = _incoming_socket->getLocalPort();
    string remote_ip = client_ip;
    int remote_port = client_port;

    cout << "Accepted Connection from " << remote_ip << ":" << remote_port << endl;

    // Create the socket with the connection to the client
    try{
        TCPSocket client_socket = TCPSocket(remote_ip,remote_port,server_ip,server_port);
        client_socket.setFileDescriptor(newsockfd);
        connections.push_back(client_socket);
    }catch (char const * e){
        throw e;
    }
    return (connections.size()-1);
}

int TCPSocketServer::listen() throw(char const *){
    if (_listening) return 0;

    if (!_binded) {
        try{
            bind();
        }catch(string e){
            throw e;
        }
    }

    int inc_soket_fd = _incoming_socket->getFileDescriptor();

    int status = ::listen(inc_soket_fd,_max_connections);
    if (status != 0){
        _listening=false;
        throw("Error listening");
        return status;
    }

    cout << "server fd: " << inc_soket_fd  << " listening." << endl;

    _listening = true;
    return status;
}

